#region License, Terms and Conditions
//
// Jayrock - A JSON-RPC implementation for the Microsoft .NET Framework
// Written by Atif Aziz (www.raboof.com)
// Copyright (c) Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Jayrock.Json.Conversion.Generation
{
    #if !NET_1_0 && !NET_1_1 && !NET_2_0

    #region Imports

    using System;
    using NUnit.Framework;

    #endregion

    [TestFixture]
    public class TestJsonImport
    {
        static Func<string, T> AssertingSuccess<T>(JsonImportAutomaton<T> automaton)
        {
            Assert.IsNotNull(automaton);
            return json =>
            {
                var state = ImportState.Create(JsonText.CreateReader(json));
                Assert.IsFalse(state.IsInitialized);
                var value = automaton(state);
                Assert.IsTrue(state.IsInitialized);
                Assert.IsTrue(state.Success);
                return value;
            };
        }

        static Func<string, Exception> AssertingError<T>(JsonImportAutomaton<T> automaton)
        {
            Assert.IsNotNull(automaton);
            return json =>
            {
                var state = ImportState.Create(JsonText.CreateReader(json));
                Assert.IsFalse(state.IsInitialized);
                automaton(state);
                Assert.IsTrue(state.IsInitialized);
                Assert.IsFalse(state.Success);
                return state.Error;
            };
        }

        [Test]
        public void Content()
        {
            var token = AssertingSuccess(JsonImport.Content())("[]");
            Assert.AreEqual(JsonToken.Array(), token);
        }

        [Test, ExpectedException(typeof(JsonException))]
        public void CannotContentWhenMissingValue()
        {
            AssertingSuccess(JsonImport.Content())(string.Empty);
        }

        [Test]
        public void Return()
        {
            var importer = JsonImport.Return(42);
            Assert.AreEqual(42, AssertingSuccess(importer)("null"));
        }

        [Test]
        public void Then()
        {
            var importer = JsonImport.Return(42)
                                     .Then(v => JsonImport.Return(v * 2));
            Assert.AreEqual(42 * 2, AssertingSuccess(importer)(string.Empty));
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void CannotRefWithNullArray()
        {
            JsonImport.Ref((JsonImportAutomaton<object>[]) null);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void CannotRefWithMoreThanOneCell()
        {
            JsonImport.Ref(new JsonImportAutomaton<object>[2]);
        }

        [Test, ExpectedException(typeof(ArgumentException))]
        public void CannotRefWithZeroCells()
        {
            JsonImport.Ref(new JsonImportAutomaton<object>[0]);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void CannotRefWithNullDereferencer()
        {
            JsonImport.Ref((Func<JsonImportAutomaton<object>>)null);
        }

        [Test]
        public void ThenFirstError()
        {
            var e = new JsonException();
            var importer = JsonImport.ReturnError<int>(e)
                                     .Then(v => JsonImport.Return(v * 2));
            Assert.AreEqual(e, AssertingError(importer)(string.Empty));
        }

        [Test]
        public void ThenSecondError()
        {
            int? first = null;
            var e = new JsonException();

            var importer = 
                JsonImport.Return(42)
                          .Then(v =>
                          {
                              first = v; 
                              return JsonImport.ReturnError<int>(e);
                          });
            
            Assert.AreEqual(e, AssertingError(importer)(string.Empty));
            Assert.AreEqual(42, first);
        }

        [Test]
        public void Ref()
        {
            var impref = new JsonImportAutomaton<int>[1];
            var importer = JsonImport.Ref(impref);
            impref[0] = JsonImport.Return(42);
            Assert.AreEqual(42, AssertingSuccess(importer)(string.Empty));
        }

        [Test]
        public void OrLeft()
        {
            var importer = 
                JsonImport.Return(42)
                          .Or(JsonImport.ReturnError<int>(new JsonException()));
            Assert.AreEqual(42, AssertingSuccess(importer)(string.Empty));
        }

        [Test]
        public void OrRight()
        {
            var importer =
                JsonImport.ReturnError<int>(new JsonException())
                          .Or(JsonImport.Return(42));
            Assert.AreEqual(42, AssertingSuccess(importer)(string.Empty));
        }

        [Test]
        public void OrNeither()
        {
            var lhs = new JsonException();
            var rhs = new JsonException();
            var importer = JsonImport.ReturnError<object>(lhs).Or(JsonImport.ReturnError<object>(rhs));
            Assert.AreEqual(rhs, AssertingError(importer)(string.Empty));
        }

        [Test]
        public void Null()
        {
            Assert.IsNull(AssertingSuccess(JsonImport.Null())("null"));
        }

        [Test]
        public void NullError()
        {
            AssertingError(JsonImport.Null())("42");
        }

        [Test]
        public void String()
        {
            Assert.AreEqual("foobar", AssertingSuccess(JsonImport.String())("foobar"));
        }

        [Test]
        public void StringError()
        {
            AssertingError(JsonImport.String())("null");
        }

        [Test]
        public void Number()
        {
            Assert.AreEqual(123, (int) AssertingSuccess(JsonImport.Number())("123"));
        }

        [Test]
        public void NumberError()
        {
            AssertingError(JsonImport.Number())("null");
        }

        [Test]
        public void BooleanTrue()
        {
            Assert.IsTrue(AssertingSuccess(JsonImport.Boolean())("true"));
        }

        [Test]
        public void BooleanFalse()
        {
            Assert.IsFalse(AssertingSuccess(JsonImport.Boolean())("false"));
        }

        [Test]
        public void BooleanError()
        {
            AssertingError(JsonImport.Boolean())("null");
        }

        [Test]
        public void Array()
        {
            var values = AssertingSuccess(JsonImport.String().Array())("[foo,bar,baz]");
            Assert.IsNotNull(values);
            using (var e = values.GetEnumerator())
            {
                Assert.IsTrue(e.MoveNext()); Assert.AreEqual("foo", e.Current);
                Assert.IsTrue(e.MoveNext()); Assert.AreEqual("bar", e.Current);
                Assert.IsTrue(e.MoveNext()); Assert.AreEqual("baz", e.Current);
                Assert.IsFalse(e.MoveNext());
            }
        }

        [Test]
        public void ArrayError()
        {
            AssertingError(JsonImport.Null().Array())("null");
        }

        [Test]
        public void OrNull()
        {
            var importer = JsonImport.String().OrNull();
            Assert.IsNull(AssertingSuccess(importer)("null"));
            Assert.AreEqual("foobar", AssertingSuccess(importer)("foobar"));
        }

        [Test]
        public void NullOr()
        {
            var importer = JsonImport.String().OrNull();
            Assert.IsNull(AssertingSuccess(importer)("null"));
            Assert.AreEqual("foobar", AssertingSuccess(importer)("foobar"));
        }

        [Test]
        public void Select()
        {
            var importer = JsonImport.Return("foobar")
                                     .Select(s => s.ToUpperInvariant());
            Assert.AreEqual("FOOBAR", AssertingSuccess(importer)(string.Empty));
        }

        [Test]
        public void SelectError()
        {
            var error = new JsonException();
            var importer = JsonImport.ReturnError<object>(error)
                                     .Select(o => o.ToString());
            Assert.AreEqual(error, AssertingError(importer)(string.Empty));
        }

        [Test]
        public void OrNullable()
        {
            var importer = JsonImport.Number().OrNullable();
            var number = (int?) AssertingSuccess(importer)("42");
            Assert.IsNotNull(number);
            Assert.AreEqual(42, number);
            Assert.IsNull(AssertingSuccess(importer)("null"));
        }

        // #pragma warning disable 649 // CS0649: Field is never assigned to, 
        //                             // and will always have its default value null
        class Thing
        {
            public int Integer = 0;
            public double Number = 0;
            public string Text = null;
        }
        
        // #pragma warning restore 649

        [Test]
        public void Object()
        {
            var importer = 
                JsonImport.Object<Thing>(b => b
                          .Member("int", t => t.Integer, JsonImport.Number().Select(n => (int) n))
                          .Member(t => t.Number, JsonImport.Number().Select(n => (double) n))
                          .Member(t => t.Text, JsonImport.String()));
            var thing = AssertingSuccess(importer)(@"
            { 
                text     : foobar, 
                int      : 123, 
                number   : 4.56,  
                something: else,
            }");
            Assert.IsNotNull(thing);
            Assert.AreEqual(123, thing.Integer);
            Assert.AreEqual(4.56, thing.Number);
            Assert.AreEqual("foobar", thing.Text);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void CannotValueOrErrorWithNullImporter()
        {
            JsonImport.ValueOrError(null, (object v) => v, e => null);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void CannotValueOrErrorWithNullValueSelector()
        {
            JsonImport.ValueOrError(s => (object) null, null, e => (object) null);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void CannotValueOrErrorWithNullErrorSelector()
        {
            JsonImport.ValueOrError(s => (object) null, v => v, null);
        }

        [Test]
        public void ValueOrError()
        {
            var importer = JsonImport.Return(42)
                                     .ValueOrError(v => (object) v, e => null);
            Assert.IsNotNull(importer);
            Assert.AreEqual(42, importer(JsonText.CreateReader(string.Empty)));
        }

        [Test]
        public void Value()
        {
            var importer = JsonImport.Return(42).Value();
            Assert.IsNotNull(importer);
            Assert.AreEqual(42, importer(JsonText.CreateReader(string.Empty)));
        }

        [Test, ExpectedException(typeof(JsonException))]
        public void ValueThrowsWhenError()
        {
            var importer = JsonImport.ReturnError<int>().Value();
            Assert.IsNotNull(importer);
            importer(JsonText.CreateReader(string.Empty));
        }
    }

    #endif // !NET_1_0 && !NET_1_1 && !NET_2_0
}